package org.tradeinterceptor.modular.strategy;

import java.awt.Color;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import com.riflexo.charting.api.AbstractEasyCalculator;
import com.riflexo.charting.api.Line;
import com.riflexo.charting.api.StockIndicator;
import com.riflexo.charting.api.plugin.annotation.Strategy;
import com.riflexo.trading.api.TradingException;

@Strategy(name = "Modular Strategy", autoCommit = false)
public class ModularStrategy extends AbstractEasyCalculator {

	private Logger logger;

	// 80% of the indicators should agree
	double OPEN_LIMIT = 0.8;
	double CLOSE_LIMIT = 0.01;

	private StockIndicator<Object> movingAverage200;
	private Line<Object> ema200;

	private Line<Object> buySignals;
	private Line<Object> sellSignals;
	private Line<Object> closeSignals;

	List<StrategyModule> modules;

	boolean inPosition = false;
	double positionPrice = 0;
	ForexSignal positionDirection;

	double cumulatedProfit = 0;

	double trailingStop = 0;

	StrategyInitializer initializer;

	@Override
	public void calculate() {
		try {

			Date time = getMainSeries().getDataItem().getTime();
			String quote = getMainSeries().getRequest().getQuoteCode();
			int interval = getMainSeries().getRequest().getTimescale();

			Double price = getMainSeries().getDataItem().getClose();

			if (inPosition) {
				double result = calculateProfit(price);

				if (result < 0 && result <= 0 - trailingStop) {
					inPosition = false;
					logger.info("Trailing stop reached");
					closeTrade(price, time, quote, interval);
					return;
				} else if (result > 0
						&& result >= initializer
								.getTakeProfit()) {
					inPosition = false;
					logger.info("Profit target reached");
					closeTrade(price, time, quote, interval);
					return;
				} else if (result > 0) {
					trailingStop += result;
				}
			}

			ema200.set(movingAverage200.get());

			if (!getMainSeries().getDataItem().isFullyClosed()) {
				return;
			}

			// Checking the bulls...
			ForexSignal signal = getSignal();

			switch (signal) {
			case BUY:
				if (!inPosition) {
					openTrade(time, quote, interval, ForexSignal.BUY, price);
				}
				break;
			case SELL:
				if (!inPosition) {
					openTrade(time, quote, interval, ForexSignal.SELL, price);
				}
				break;
			case CLOSE:
				if (inPosition) {
					closeTrade(price, time, quote, interval);
				}
				break;
			}

		} catch (TradingException _ex) {
			logger.warning("Exception occurred: " + _ex);
		}
	}

	private void closeTrade(double price, Date time, String quote, int interval) {
		closeSignals.set(price);
		double profit = calculateProfit(price);
		cumulatedProfit = cumulatedProfit + profit;
		inPosition = false;
		notifyPrice(time, quote, interval, ForexSignal.CLOSE, price);
		for (StrategyModule m : modules) {
			m.close();
		}
		logger.info("Profit taken: " + profit + "\n");
		logger.info("Cumulated profit: " + cumulatedProfit + "\n");
	}

	private void openTrade(Date time, String quote, int interval,
			ForexSignal signal, Double price){
		switch (signal) {
		case BUY:
			buySignals.set(price);
			break;
		case SELL:
			sellSignals.set(price);
			break;
		}
		inPosition = true;
		positionPrice = price;
		positionDirection = signal;
		for (StrategyModule m : modules) {
			m.setPosition(signal);
		}
		notifyPrice(time, quote, interval, positionDirection, price);
	}
	
	private void notifyPrice(Date time, String quote, int interval,
			ForexSignal signal, Double price) {
		if (time.getTime() > new Date().getTime() - interval * 60000) {
			initializer.getNotifier().sendNotification(
					"ModularStrategy: " + signal.toString() + " signal for "
							+ quote, "\nCurrency pair: " + quote + "\nScale: "
							+ interval + "\nTime: " + time + "\nPrice: "
							+ price);
		}
		logger.info(signal.toString() + " signal\nCurrency pair: " + quote
				+ "\nTime: " + time + "\nScale: " + interval + "\nPrice: "
				+ price + "\n");
	}

	private double calculateProfit(double closingPrice) {
		double retVal = 0;
		switch (positionDirection) {
		case BUY:
			retVal = closingPrice - positionPrice;
			break;
		case SELL:
			retVal = positionPrice - closingPrice;
			break;
		}
		return retVal;
	}

	private ForexSignal getSignal() {
		double buyers = 0;
		double sellers = 0;
		double closers = 0;
		double momentum = 0;
		double standaside = 0;
		logger.info(getMainSeries().getDataItem().getTime() + "------------------------------------Start calculating signal-----------------------------------------");
		ForexSignal signal = ForexSignal.NONE;
		for (StrategyModule m : modules) {
			ForexSignal modSignal = m.getSignal();
			logger.info(getMainSeries().getDataItem().getTime() + " Voter: "
					+ m.getClass().toString().replaceAll(".*\\.", "") + " : "
					+ modSignal);
			switch (modSignal) {
			case BUY:
				buyers++;
				logger.info("BUY");
				break;
			case SELL:
				sellers++;
				logger.info("SELL");
				break;
			case CLOSE:
				closers++;
				logger.info("CLOSE");
				break;
			case MOMENTUM:
				momentum++;
				logger.info("MOMENTUM");
				break;
			case STAND_ASIDE:
				standaside++;
				logger.info("STAND_ASIDE");
				break;
			}
		}
		double allModules = modules.size();
		double allVoters = buyers + sellers + closers + momentum + standaside;
		logger.info("All: " + allVoters + " sellers: " + sellers + " buyers: "
				+ buyers + " closers: " + closers + " momentum: " + momentum);
		if ((closers + standaside) / allModules >= CLOSE_LIMIT) {
			signal = ForexSignal.CLOSE;
		} else if (((buyers + momentum) / allVoters >= OPEN_LIMIT)
				&& (buyers >= 1) && (buyers > sellers)) {
			signal = ForexSignal.BUY;
		} else if (((sellers + momentum) / allVoters >= OPEN_LIMIT)
				&& (sellers >= 1) && (sellers > buyers)) {
			signal = ForexSignal.SELL;
		}
		logger.info(getMainSeries().getDataItem().getTime()
				+ " Acumulated signal: " + signal.toString());
		logger.info(getMainSeries().getDataItem().getTime() + "------------------------------------End calculating signal-----------------------------------------");
		return signal;
	}

	@Override
	public void declare() {
		logger = getLogger();
		
		initialize();

		modules = initializer.getModules();

		movingAverage200 = getStockIndicatorUtil()
				.declareExponentialMovingAverage("ema200", 200);
		ema200 = declareOutput("EMA 200");
		ema200.setDefaultColor(Color.blue);

		buySignals = declareOutput("Buy signals");
		buySignals.setDefaultColor(Color.blue);
		buySignals.setVisualType(Line.VisualType.DOTS_RENDERER);
		sellSignals = declareOutput("Sell signals");
		sellSignals.setDefaultColor(Color.magenta);
		sellSignals.setVisualType(Line.VisualType.DOTS_RENDERER);
		closeSignals = declareOutput("Close signals");
		closeSignals.setDefaultColor(Color.black);
		closeSignals.setVisualType(Line.VisualType.DOTS_RENDERER);

		this.trailingStop = initializer.getTrailingStop();
		
		OPEN_LIMIT = initializer.getModulesOpenRatio();
		CLOSE_LIMIT = initializer.getModuleCloseRatio();
		
		for (StrategyModule module : modules) {
			module.declare(this);
		}

		// Output lines
		setStandAlone(false); // place it in the main pane

		setName("Combined Proprietary Strategy");

	}

	/**
	 * Reads the strategy properties and gets the strategy initializer
	 */
	private void initialize(){
		Properties properties = new Properties();
		try {
			properties.load(new FileInputStream("modularstrategy.properties"));
		} catch (IOException e) {
			throw new ModularStrategyException("Cannot read the properties from modularstrategy.properties",e);
		}
		if(! properties.containsKey("org.tradeinterceptor.modular.strategy.initializer")){
			throw new ModularStrategyException("Cannot get the initializer class from the property file.");
		}
		
		Class<?> c;
		String initializerClass = properties.get("org.tradeinterceptor.modular.strategy.initializer").toString();
		try {
			c = Class.forName(initializerClass);
			initializer = (StrategyInitializer) c.newInstance();
			initializer.setProperties(properties);
		} catch (ClassNotFoundException e) {
			throw new ModularStrategyException("Cannot load the strategy initializer class: " + initializerClass,e);
		} catch (InstantiationException e) {
			throw new ModularStrategyException("Cannot instantiate the strategy initializer class: " + initializerClass,e);
		} catch (IllegalAccessException e) {
			throw new ModularStrategyException(e);
		}
	}

}
